﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace SharpSoft.WPF.Controls
{
    /// <summary>
    /// 力学容器
    /// </summary>
    public class MechanicsCanvas : Canvas
    {

        public MechanicsCanvas()
        {
            this._timeTracker = new TimeTracker();
            this._velocities = new Dictionary<UIElement, Vector>();

            this.Loaded += MechanicsCanvas_Loaded;
            this.Unloaded += MechanicsCanvas_Unloaded;
        }

        private void MechanicsCanvas_Unloaded(object sender, RoutedEventArgs e)
        {
            if (!DesignerProperties.GetIsInDesignMode(this))
            {
                CompositionTarget.Rendering -= CompositionTarget_Rendering;
            }
        }

        private void MechanicsCanvas_Loaded(object sender, RoutedEventArgs e)
        {
            if (!DesignerProperties.GetIsInDesignMode(this))
            {
                CompositionTarget.Rendering += CompositionTarget_Rendering;
            }
        }

        #region 成员
        private readonly TimeTracker _timeTracker;
        /// <summary>
        /// 储存元素的速度
        /// </summary>
        private readonly Dictionary<UIElement, Vector> _velocities;
        #endregion

        #region 附加属性

        public static Point GetOriginalPosition(DependencyObject obj)
        {
            return (Point)obj.GetValue(OriginalPositionProperty);
        }

        public static void SetOriginalPosition(DependencyObject obj, Point value)
        {
            obj.SetValue(OriginalPositionProperty, value);
        }
        /// <summary>
        /// 附加属性，用于记录子UI的原始位置
        /// </summary>
        public static readonly DependencyProperty OriginalPositionProperty =
            DependencyProperty.RegisterAttached("OriginalPosition", typeof(Point), typeof(MechanicsCanvas), new PropertyMetadata(new Point(double.NaN, double.NaN), new PropertyChangedCallback(OnOriginalPositionChanged)));

        private static void OnOriginalPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            UIElement ele = (UIElement)d;
            var x = Canvas.GetLeft(ele);
            var y = Canvas.GetTop(ele);
            var p = (Point)e.NewValue;
            if (double.IsNaN(x))
            {
                Canvas.SetLeft(ele, p.X);
            }
            if (double.IsNaN(y))
            {
                Canvas.SetLeft(ele, p.Y);
            }
        }

        #endregion

        #region 属性

        /// <summary>
        /// 边框的斥力
        /// </summary>
        public double BorderForce { get; set; } = 0;

        /// <summary>
        /// 元素间的斥力
        /// </summary>
        public double ChildForce { get; set; } = 0;

        /// <summary>
        /// 定位力度（当元素位移后，始终存在一个力迫使元素回到其初始位置）
        /// </summary>
        public double PositioningForce { get; set; } = 100;

        /// <summary>
        /// 拉力
        /// </summary>
        public double Drag { get; set; } = 0.5;

        #endregion

        #region 方法
        /// <summary>
        /// 强行设置元素的即时速度
        /// </summary>
        /// <param name="element"></param>
        /// <param name="velocity"></param>
        public void SetVelocity(UIElement element, Vector velocity)
        {
            if (this.Children.Contains(element))
            {
                this._velocities[element] = velocity;
            }

        }
        #endregion

        #region 实现

        private void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            this._timeTracker.Update();
            UpdateChildren();
        }
        private void UpdateChildren()
        {
            var children = LogicalTreeHelper.GetChildren(this);
            foreach (UIElement child in children)
            {
                var velocity = _velocities.ContainsKey(child) ? _velocities[child] : new Vector(0, 0);
                velocity *= this.Drag;
                var pos = GetPosition(child);
                var childRect = new Rect(pos, child.RenderSize);
                //计算元素上的力
                var forces = new Vector(0, 0);

                if (BorderForce != 0d)
                {//墙的排斥力 
                    forces.X += BorderForce / Math.Max(1, childRect.Left);
                    forces.X -= BorderForce / Math.Max(1, RenderSize.Width - childRect.Right);
                    forces.Y += BorderForce / Math.Max(1, childRect.Top);
                    forces.Y -= BorderForce / Math.Max(1, RenderSize.Height - childRect.Bottom);
                }

                if (ChildForce != 0d)
                { //计算其他子元素对当前元素的排斥力
                    foreach (UIElement otherchild in children)
                    {
                        //dont push against itself
                        if (otherchild == child)
                            continue;//排除自身

                        var otherchildtruePosition = GetPosition(otherchild);
                        var otherchildRect = new Rect(otherchildtruePosition, otherchild.RenderSize);

                        //make sure rects aren't the same
                        if (otherchildRect == childRect)
                            continue;

                        //忽略尺寸为0的子元素
                        if (otherchildRect.Width == 0 && otherchildRect.Height == 0 ||
                            childRect.Width == 0 && childRect.Height == 0)
                            continue;

                        //vector from current other child to current child
                        //are they overlapping?  if so, distance is 0
                        var toChild = AreRectsOverlapping(childRect, otherchildRect)
                            ? new Vector(0, 0)
                            : VectorBetweenRects(childRect, otherchildRect);

                        var length = toChild.Length;
                        if (length < 1)
                        {//过于接近的元素使用它们矩形的中心计算推力
                            length = 1;
                            var childCenter = GetCenter(childRect);
                            var otherchildCenter = GetCenter(otherchildRect);
                            //compute toChild from the center of both rects
                            toChild = childCenter - otherchildCenter;
                        }

                        var childpush = ChildForce / length;//元素间的推力，距离越远推力越小

                        toChild.Normalize();
                        forces += toChild * childpush;
                    }
                }

                if (PositioningForce != 0d)
                {//计算回位力
                    var op = GetOriginalPosition(child);//原始位置
                    var toOriginal = op - pos;
                    var length = toOriginal.Length;

                    var pospush = PositioningForce * length / 100d;//元素的回位力，距离越远回位力越大
                    toOriginal.Normalize();
                    forces += toOriginal * pospush;

                }

                velocity += forces;
                if (double.IsNaN(velocity.X))
                {
                    velocity.X = 0;
                }
                if (double.IsNaN(velocity.Y))
                {
                    velocity.Y = 0;
                }
                _velocities[child] = velocity;

                SetPosition(child, pos + _timeTracker.DeltaSeconds * velocity);
            }
        }
        private void SetPosition(UIElement element, Point p)
        {
            Canvas.SetLeft(element, double.IsNaN(p.X) ? 0 : p.X);
            Canvas.SetTop(element, double.IsNaN(p.Y) ? 0 : p.Y);
        }
        private Point GetPosition(UIElement element)
        {
            var x = Canvas.GetLeft(element);
            var y = Canvas.GetTop(element);
            return new Point(double.IsNaN(x) ? 0 : x, double.IsNaN(y) ? 0 : y);
        }
        private bool AreRectsOverlapping(Rect r1, Rect r2)
        {
            if (r1.Bottom < r2.Top) return false;
            if (r1.Top > r2.Bottom) return false;

            if (r1.Right < r2.Left) return false;
            if (r1.Left > r2.Right) return false;

            return true;
        }
        private Point IntersectInsideRect(Rect r, Point raystart, Vector raydir)
        {
            var xtop = raystart.X + raydir.X * (r.Top - raystart.Y) / raydir.Y;
            var xbottom = raystart.X + raydir.X * (r.Bottom - raystart.Y) / raydir.Y;
            var yleft = raystart.Y + raydir.Y * (r.Left - raystart.X) / raydir.X;
            var yright = raystart.Y + raydir.Y * (r.Right - raystart.X) / raydir.X;
            var top = new Point(xtop, r.Top);
            var bottom = new Point(xbottom, r.Bottom);
            var left = new Point(r.Left, yleft);
            var right = new Point(r.Right, yright);
            var tv = raystart - top;
            var bv = raystart - bottom;
            var lv = raystart - left;
            var rv = raystart - right;
            //classify ray direction
            if (raydir.Y < 0)
            {
                if (raydir.X < 0) //top left
                {
                    if (tv.LengthSquared < lv.LengthSquared)
                        return top;
                    return left;
                }
                if (tv.LengthSquared < rv.LengthSquared)
                    return top;
                return right;
            }
            if (raydir.X < 0) //bottom left
            {
                if (bv.LengthSquared < lv.LengthSquared)
                    return bottom;
                return left;
            }
            if (bv.LengthSquared < rv.LengthSquared)
                return bottom;
            return right;
        }

        private Vector VectorBetweenRects(Rect r1, Rect r2)
        {
            var r1Center = GetCenter(r1);
            var r2Center = GetCenter(r2);
            var between = (r1Center - r2Center);
            between.Normalize();
            var edge1 = IntersectInsideRect(r1, r1Center, -between);
            var edge2 = IntersectInsideRect(r2, r2Center, between);
            return edge1 - edge2;
        }

        private Point GetCenter(Rect r) => new Point((r.Left + r.Right) / 2.0, (r.Top + r.Bottom) / 2.0);

        #endregion

    }
}
